<?php

version_compare(PHP_VERSION, '5.6.0', '>=') || die('requires PHP 5.6.0+, Please upgrade!');

define('IN_XFRAME', true);
defined('DS') || define('DS', DIRECTORY_SEPARATOR);
define('CONTROLLER_SUFFIX', 'Controller');
define('CONTROLLERS_NAMESPACE', 'app\controllers\\');

date_default_timezone_set('PRC');

class Xframe
{
	private static function init()
	{
		switch (PHP_ENV) {
			case 'development':
				error_reporting(-1);
				ini_set('display_errors', \Config::get("app_debug", 1));
				break;

			case 'testing':
				error_reporting(-1);
				ini_set('display_errors', \Config::get("app_debug", 0));
				break;

			case 'production':
				ini_set('display_errors', \Config::get("app_debug", 0));
				if (version_compare(PHP_VERSION, '5.3', '>=')) {
					error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
				} else {
					error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE);
				}
				break;

			default:
				header('HTTP/1.1 503 Service Unavailable.', TRUE, 503);
				echo 'The application environment is not set correctly.';
				exit(1);
		}

		(new ExceptionX())->SetExceptionHandler();
	}

	public static function run()
	{
		self::init();
		Config::get();
		$thisRoute = Route::getRoute();
		$controller = CONTROLLERS_NAMESPACE . $thisRoute->path . $thisRoute->controller . CONTROLLER_SUFFIX;
		$controllerObj = new $controller();
		if (!is_callable(array($controllerObj, $thisRoute->method))) {
			throw new \Exception('Method cannot be called: '. $thisRoute->method, 405);
		}
		$result = call_user_func_array(array($controllerObj, $thisRoute->method), $thisRoute->params);
		return self::response($result);
	}

	public static function response($data, $code = 200, $headers = [])
	{
		if(is_array($data) || is_object($data)) {
			echo json_encode($data, JSON_UNESCAPED_UNICODE);
		} else {
			print_r($data);
		}
	}
}


class Route
{
	private static $index;

	//这些目录下的 php 禁止访问
	protected static $exclude = ['js','css','img','image','static','upload','storage','tmp','temp','cache','logs'];

	/**
	 * 根据 URI 来定位控制器，判断控制器是否存在
	 * @return object $route
	 * @throws Exception
	 */
	public static function getRoute()
	{
		$route = (object)[
			'index' => self::getIndex(),
			'path' => '',
			'controller' => '',
			'method' => '',
			'params' => [],
			'segments' => [],
		];

		$_path = self::getPath();

		if (empty($_path)) {
			$route->controller = 'Index';
			$route->method = 'index';
			return $route;
		}
		$segments = array_filter(explode('/', $_path));
		unset($_path);
		$segmentsCount = count($segments);

		if ($segmentsCount > 10) { //目录太深，或参数太多
			throw new \Exception('URL非法', 501);
		}

		foreach ($segments as $segment) {
			foreach (self::$exclude as $dir) {
				if (stristr($segment, $dir)) {
					exit('Forbidden 403');
				}
			}
		}

		switch ($segmentsCount) {

			case 1:
				$route->controller = line_to_hump($segments[0], true);
				$route->method = 'index';
				break;

			case 2:
				$route->controller = line_to_hump($segments[0], true);
				$route->method = $segments[1];
				break;

			default:
				for ($pos = 0; $pos < $segmentsCount; $pos++) {
					$controller = line_to_hump($segments[$pos], true);
					$pathArr = array_slice($segments, 0, $pos);
					if ($pathArr) {
						$route->path = join('\\', $pathArr) . '\\';
					}
					if (class_exists(CONTROLLERS_NAMESPACE . $route->path . $controller . CONTROLLER_SUFFIX)) {
						$route->controller = $controller;
						$route->method = isset($segments[$pos + 1]) ? $segments[$pos + 1] : "index";
						$route->params = array_slice($segments, $pos + 2);
					}
				}
		}

		if (!class_exists(CONTROLLERS_NAMESPACE . $route->path . $route->controller . CONTROLLER_SUFFIX)) {
			throw new \Exception('File not found: ' . $route->path . $route->controller, 404);
		}

		return $route;
	}

	private static function getUri()
	{
		if (isset($_SERVER["REQUEST_URI"])) {
			$uri = $_SERVER["REQUEST_URI"];
		} else {
			$uri = $_SERVER['PHP_SELF'];
			if (!empty($_SERVER["QUERY_STRING"])) {
				$uri .= '?' . $_SERVER["QUERY_STRING"];
			}
		}
		$uri = str_replace("\\", "/", $uri);
		$uri = str_replace("//", "/", $uri);
		$uri = str_replace("/index.php", "", $uri);
		return $uri;
	}

	private static function getPath()
	{
		$thisUri = self::getUri();
		$path = str_replace(strstr($thisUri, '.'), '', $thisUri);
		$path = str_replace(strstr($path, '?'), '', $path);
		$path = str_replace(strstr($path, '#'), '', $path);
		if($path == "/") {
			return "";
		}
		$index = self::getIndex();
		$path = $index == "" ? $path : strrev(stristr(strrev($path), strrev($index), true));
		$path = trim($path, '/');
		if (!preg_match('#^[\w\-\_\/]*$#i', $path)) {
			throw new \Exception('URL非法', 501);
		}
		return $path;
	}

	/**
	 * 获取 入口文件的 URI
	 * @return string. 如："" 或者 "/xxx"
	 */
	public static function getIndex()
	{
		if(self::$index) {
			return self::$index;
		}
		$docRoot = empty($_SERVER['DOCUMENT_ROOT']) ? ROOT_PATH : str_replace("\\", "/", $_SERVER['DOCUMENT_ROOT']);
		$index = str_replace(rtrim($docRoot, '/'), "", rtrim(INDEX_PATH, '/'));
		return $index;
	}
}

/**
 * PHP Angular 模板引擎
 * 源码：
 * https://github.com/php-angular/php-angular
 * 手册：
 * https://www.kancloud.cn/shuai/php-angular/127397
 *
	$data = [
		'title' => 'Hello PHP Angular',
		'list'  => [
			['name' => 'name_1', 'email' => 'email_1@qq.com'],
			['name' => 'name_2', 'email' => 'email_2@qq.com'],
			['name' => 'name_3', 'email' => 'email_3@qq.com'],
			['name' => 'name_4', 'email' => 'email_4@qq.com'],
			['name' => 'name_5', 'email' => 'email_5@qq.com'],
		],
	];
	$tpl = "test";
	\View::display($tpl, $data);
 *
 */
class View
{
	private static $config = [
		'debug' => true,
		'tpl_path' => APP_PATH . 'views/',
		'tpl_suffix' => '.php',
		'tpl_cache_path' => ROOT_PATH . 'storage/views/',
		'tpl_cache_suffix' => '.php',
		'directive_prefix' => 'php-',    // 指令前缀
		'directive_max' => 10000,    // 指令的最大解析次数
	];

	private static $view;

	/**
	 * 向模板赋值，并输出解析结果
	 * @param $tpl
	 * @param array $data
	 * @return mixed
	 */
	public static function display($tpl, $data = [])
	{
		return static::assign($data)->display(static::tpl($tpl));
	}

	/**
	 * 向模板赋值，并返回编译解析后的html结果
	 * @param $tpl
	 * @param $data
	 * @return mixed
	 */
	public static function fetch($tpl, $data = [])
	{
		return static::assign($data)->fetch(static::tpl($tpl));
	}

	/**
	 * 获取模板编译后的php代码
	 * @param $tpl
	 * @param array $data
	 * @return mixed
	 */
	public static function compiler($tpl, $data = [])
	{
		return static::instance()->compiler(static::tpl($tpl));
	}

	/**
	 * 向模板引擎设置数据
	 * @param $data
	 * @return mixed
	 */
	public static function assign($data)
	{
		static::instance()->assign($data);
		return static::$view;
	}

	/**
	 * 获取模板路径（自动判断手机端、PC端）
	 * @param $tpl
	 * @return string
	 */
	public static function tpl($tpl)
	{
		$tpl = $tpl ? $tpl : 'index';
		$tpl = is_mobile() ? 'm/' . $tpl : 'pc/' . $tpl;
		return $tpl;
	}

	public static function instance()
	{
		static::$view = !empty(static::$view) ? static::$view : (new \PHPAngular\Angular(static::$config));
		return static::$view;
	}
}

class Config
{
	private static $config = [];

	private static $files = [];

	/**
	 * 加载配置
	 * @param string $name : 配置名
	 * @return bool
	 */
	public static function load($name)
	{
		$name = trim($name, '.');
		if (!preg_match('#^[\w\-\.]*$#i', $name)) {
			return false;
		}

		$config = false;
		$file = str_replace(strstr($name, '.'), '', $name);
		$file = $file ? trim($file) : 'config';

		if (!isset(static::$files[$file])) {
			$fileName = CONFIG_PATH . $file . '.php';
			if (is_file($fileName)) {
				static::$files[$file] = 1;
				$config = include $fileName;
			}
		}

		$fileEnv = PHP_ENV . '/' . $file;
		if (!isset(static::$files[$fileEnv])) {
			$fileEnvName = CONFIG_PATH . $fileEnv . '.php';
			if (is_file($fileEnvName)) {
				static::$files[$file] = 1;
				static::$files[$fileEnv] = 1;
				$config2 = include $fileEnvName;
				$config = $config ? (array_replace_recursive($config, $config2)) : $config2;
			}
		}

		if ($config) {
			static::$config[$file] = isset(static::$config[$file]) ? array_replace_recursive(static::$config[$file], $config) : $config;
		}

		return $config;
	}

	/**
	 * 获取配置参数，支持无限层，健名区分大小写
	 * 注意：第一个 "." 左边的部分视为配置文件名，默认为 config.
	 * \Config::get();
	 * \Config::get("app_id");
	 * \Config::get("config.app_id");
	 * \Config::get("config.a.b.c");
	 *
	 * @param string $name
	 * @param null $defaultValue
	 * @return mixed|null
	 */
	public static function get($name = '', $defaultValue = null)
	{
		$name = trim($name, '.');
		$loaded = static::load($name);
		if (is_null($name) || $name === '') {
			return array_get("config", static::$config);
		}
		if ($loaded === false && preg_match('#^[\w\-\.]*$#i', $name) && strstr($name, 'config') === false) {
			$name = 'config.' . $name;
			static::load($name);
		}
		$config = array_get($name, static::$config);
		return !is_null($config) ? $config : $defaultValue;
	}

	/**
	 * 动态设置配置参数，健名区分大小写
	 * \Config::set("config.a.b.c", "abc");
	 *
	 * @param string $name 配置变量
	 * @param mixed $value 配置值
	 * @return bool
	 */
	public static function set($name = null, $value = null)
	{

	}
}

/**
 * 全局异常捕获处理类
 */
class ExceptionX
{
	public function SetExceptionHandler()
	{
		set_error_handler([$this, 'errorHandler']);
		set_exception_handler([$this, 'exceptionHandler']);
		register_shutdown_function([$this, 'shutdownHandler']);
	}

	/**
	 * 记录错误日志到数据库、Log文件，你自己写吧！
	 * @param $log
	 */
	public function addLog($log)
	{
		
	}

	public function errorHandler($severity, $message, $file, $line, $context = [])
	{
		if ((error_reporting() & $severity) !== $severity) {
			return;
		}

		$err["error"] = $severity;
		$err["file"] = $file;
		$err["line"] = $line;
		$err["msg"] = $message;
		$err["context"] = $context;

		//如果显示错误信息
		if (str_ireplace(array('off', 'none', 'no', 'false', 'null', '0'), '', ini_get('display_errors'))) {
			prin($err, 0);
		}

		$log = [
			'type' => "errorHandler",
			'content' => print_r($err, true),
			'time' => date("Y-m-d H:i:s"),
		];

		$this->addLog($log);

		if (((E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR) & $severity) === $severity) {
			exit(1); // EXIT_ERROR
		}
	}

	public function exceptionHandler($e)
	{
		$err["error"] = get_class($e);
		$err["code"] = $e->getCode();
		$err["file"] = $e->getFile();
		$err["line"] = $e->getLine();
		$err["msg"] = $e->getMessage();
		//$err["trace"] = $e->getTrace();
		$err["trace"] = $e->getTraceAsString();

		$log = [
			'type' => "exceptionHandler",
			'content' => print_r($err, true),
			'time' => date("Y-m-d H:i:s"),
		];

		$this->addLog($log);

		//如果显示错误信息
		if (str_ireplace(array('off', 'none', 'no', 'false', 'null', '0'), '', ini_get('display_errors'))) {
			prin($err, 0);
		}

		exit(1); // EXIT_ERROR
	}

	public function shutdownHandler()
	{
		$last_error = error_get_last();

		if ($last_error) {
			if ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) {
				$this->errorHandler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
			}
		}
	}
}

